﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YasharEl.Infrastructure.ResourcesManagement.Managers;
using System.Reflection;
using YasharEl.Infrastructure.Diagnostics.Logging;
using System.Globalization;
using System.Diagnostics.Contracts;

namespace YasharEl.Infrastructure.ResourcesManagement.Providers
{
    /// <summary>
    /// Assembly Resource Provider.
    /// </summary>
    /// <remarks>
    /// This provider loads an assembly and generates a <see cref="ExtendedComponentResourceManager" /> populated
    /// with resources embedded in the assembly or a satellite based on the resource base name within the assembly.
    /// <para>Note: a <see cref="ExtendedComponentResourceManager" /> can only manage one resource at a time even
    /// if an assembly has multiple resources. Additional Resource Managers will be required for each Resource set
    /// within an assembly.</para>
    /// </remarks>
    public class AssemblyResourceProvider : ResourceProvider
    {
        #region Fields

        private string resourceAssemblyName;
        private string assemblyExtension;

        #endregion

        #region Construction

        /// <summary>
        /// Initializes a new default instance of the <see cref="AssemblyResourceProvider"/> class.
        /// </summary>
        /// <param name="resourceAssemblyName">Name of the resource assembly.</param>
        /// <param name="resourceBaseName">Name of the resource base.</param>
        public AssemblyResourceProvider(string resourceAssemblyName, string resourceBaseName,
            string assemblyExtension, ILogger<ResourceProvider> logger)
            : this(resourceAssemblyName, resourceBaseName, assemblyExtension, false, logger)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyResourceProvider"/> class.
        /// </summary>
        /// <param name="resourceAssemblyName">Name of the resource assembly.</param>
        /// <param name="resourceBaseName">Name of the resource base.</param>
        /// <param name="isInternal">if set to <c>true</c> then the provider is for internal resources.</param>
        public AssemblyResourceProvider(string resourceAssemblyName, string resourceBaseName,
            string assemblyExtension, bool isInternal, ILogger<ResourceProvider> logger)
            : base(resourceBaseName, isInternal, logger)
        {
            // if the provider is for internal resources then validation must be bypassed,
            // otherwise we end up in circles.
            if (!isInternal)
                ValidateParameter(resourceAssemblyName, assemblyExtension);

            this.resourceAssemblyName = resourceAssemblyName;
            this.assemblyExtension = assemblyExtension;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Create a Resource Manager to manage the resource for an assembly resource provider
        /// </summary>
        /// <returns>A <see cref="ExtendedComponentResourceManager" /> instance</returns>
        /// <remarks>
        /// <para>This method makes a type of <see cref="ExtendedComponentResourceManager"/> instance publicly available
        /// based on the assembly name and base name retrieved during Initialization</para>
        /// </remarks>
        public override ExtendedComponentResourceManager CreateResourceManager()
        {
            // Check for first time use of resource manager
            if (ResourceManager == null)
            {
                // Generate a Resource Manager
                Assembly resourceAssembly = Assembly.Load(this.resourceAssemblyName);
                ResourceManager = new ExtendedComponentResourceManager(ResourceBaseName, resourceAssembly, typeof(CommonResourceSet));
                ResourceManager.IgnoreCase = true;
            }
            Logger.Info("Resource manager({0}) created...", this.resourceAssemblyName);

            return ResourceManager;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Validates the assembly name parameter.
        /// </summary>
        /// <param name="resourceAssemblyName">Name of the resource assembly.</param>
        private void ValidateParameter(string resourceAssemblyName, string assemblyExtension)
        {
            // check for a null assembly name           
            Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(resourceAssemblyName), "resourceAssemblyName could not be null or empty");

            // check for a null assembly extension
            Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(assemblyExtension), "assemblyExtension could not be null or empty");

            // check that the assembly name represents a real assembly
            Assembly resourceAssembly;
            try
            {
                resourceAssembly = Assembly.Load(resourceAssemblyName);
            }
            catch (Exception e)
            {
                string msg = String.Format(CultureInfo.CurrentCulture, "The assembly described by the assembly name {0} could not be found.", resourceAssemblyName);
                Logger.Error(msg, e);
                throw new InvalidSystemConfigurationException(msg, e);
            }

            // check that the assembly actually contains resources
            if (resourceAssembly.GetManifestResourceNames().Length == 0)
            {
                string msg = String.Format(CultureInfo.CurrentCulture, "The assembly {0} has no resources.", resourceAssemblyName);
                throw new InfrastructureCriticalException(msg);
            }
            // iterate over the available resources to find a match with the base name
            bool foundBaseName = false;
            foreach (string baseName in resourceAssembly.GetManifestResourceNames())
            {
                if (baseName == ResourceBaseName + assemblyExtension)
                {
                    foundBaseName = true;
                    break;
                }
            }

            // Check to see if a match was found
            if (!foundBaseName)
            {
                string msg = String.Format(CultureInfo.CurrentCulture, "No resource was found matching the given base name {0}.", ResourceBaseName);
                throw new InfrastructureCriticalException(msg);
            }
        }

        #endregion
    }
}
